/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package br.com.maternity.classes.dao;

import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;

/**
 *
 * @author Aluno
 */
public abstract class GenericDAO<T> {

    private final String url = "jdbc:mysql://localhost:3306/maternity";
    private final String driver = "com.mysql.jdbc.Driver";
    private final String user = "root";
    private final String pass = "3261Next";
    private Connection con;
    protected final T genericInstance;

    public abstract List<GenericDAOField> listFields();

    public abstract String tableName();

    public GenericDAO(T instanceOfT) {
        genericInstance = instanceOfT;
    }

    private void createConnection() {
        if (con == null) {
            try {
                Class.forName(driver).newInstance();
            } catch (Exception ce) {
                return;
            }

            try {
                con = DriverManager.getConnection(url, user, pass);
            } catch (Exception exception) {
                con = null;
            }
        }
    }

    private void closeConnection() {
        if (con != null) {
            try {
                con.close();
            } catch (Exception ex) {
            } finally {
                con = null;
            }
        }
    }

    public PreparedStatement getPreparedStatement(String query) {
        createConnection();
        try {
            return con.prepareStatement(query);
        } catch (Exception ex) {
            return null;
        }

    }

    public T select(String query) {
        createConnection();
        T o = null;

        try {
            Statement sts = con.createStatement();
            o = load(sts.executeQuery(query));
        } catch (Exception ex) {
            o = null;
        } finally {
            closeConnection();
        }

        return o;
    }

    public T select(PreparedStatement ps) {
        createConnection();
        T o = null;
        java.sql.ResultSet rs;

        try {
            rs = ps.executeQuery();
            o = load(rs);
        } catch (Exception ex) {
            rs = null;
        } finally {
            closeConnection();
        }

        return o;
    }

    public List<T> selectList(String query) {
        createConnection();
        List<T> l = new ArrayList<T>();

        try {
            Statement sts = con.createStatement();
            l = loadList(sts.executeQuery(query));
        } catch (Exception exception) {
        } finally {
            closeConnection();
        }

        return l;
    }

    public List<T> selectList(PreparedStatement ps) {
        createConnection();
        ResultSet rs;
        List<T> l = null;

        try {

            rs = ps.executeQuery();
            l = loadList(rs);
        } catch (Exception exception) {
            rs = null;
        } finally {
            closeConnection();
        }

        return l;
    }

    public int delete(T o) {
        createConnection();
        int i = 0;
        PreparedStatement ps = getDeletePreparedStatement(o);

        try {
            con.setAutoCommit(false);
            i = ps.executeUpdate();
            ps.close();
            con.commit();
        } catch (Exception exception) {
            try {
                con.rollback();
            } catch (SQLException ex) {
            }

        } finally {
            closeConnection();
        }

        return i;
    }

    public int update(T o) {
        createConnection();
        int i = 0;
        PreparedStatement ps = getUpdatePreparedStatement(o);

        try {
            con.setAutoCommit(false);
            i = ps.executeUpdate();
            ps.close();
            con.commit();
        } catch (Exception exception) {
            try {
                con.rollback();
            } catch (SQLException ex) {
            }

        } finally {
            closeConnection();
        }

        return i;
    }

    public boolean insert(T o) {
        createConnection();
        boolean b = false;
        PreparedStatement ps = getInsertPreparedStatement(o);

        boolean needToGetIdentityValue = false;
        for (GenericDAOField f : listFields()) {
            if (f.getPrimaryKeyMode() == GenericDAOFieldPrimaryKey.IDENTITY) {
                needToGetIdentityValue = true;
                break;
            }
        }


        try {
            con.setAutoCommit(false);
            ps.execute();
            ps.close();
            if (needToGetIdentityValue) {
                setIdentityValue(o);
            }
            con.commit();
            b = true;
        } catch (Exception exception) {
            try {
                con.rollback();
            } catch (SQLException ex) {
            }

        } finally {
            closeConnection();
        }

        return b;
    }

    private void setIdentityValue(T o) throws Exception {
        for (GenericDAOField f : listFields()) {
            if (f.getPrimaryKeyMode() == GenericDAOFieldPrimaryKey.IDENTITY) {
                Statement stm = con.createStatement();
                String query = "SELECT LAST_INSERT_ID()";
                ResultSet rs = stm.executeQuery(query);
                if (rs.next()) {
                    Long identityValue = rs.getLong(1);
                    Method m = o.getClass().getMethod(f.getSetFunctionName(), f.getFieldType());
                    if (f.getFieldType() == Long.class) {
                        m.invoke(o, new Object[]{identityValue});
                    } else {
                        m.invoke(o, new Object[]{Integer.parseInt(identityValue.toString())});
                    }

                }
                break;
            }
        }
    }

    public T load(ResultSet rs, boolean checkNextResultSet) throws Exception {
        if (listFields() == null) {
            throw new Exception("Lista de campos não preenchidos");
        }

        if (listFields().isEmpty()) {
            throw new Exception("Lista de campos não preenchidos");
        }

        if (checkNextResultSet) {
            if (!rs.next()) {
                return null;
            }
        }

        T o = (T) genericInstance.getClass().newInstance();
        Class c = o.getClass();
        for (GenericDAOField f : listFields()) {
            Method m = c.getMethod(f.getSetFunctionName(), f.getFieldType());
            m.invoke(o, new Object[]{
                        rs.getObject(f.getDatabaseFieldName())});

            /*
             * if(f.getFieldType().equals(Integer.class)){ m.invoke(o, new
             * Object[]{ rs.getInteger(f.getDataBaseName())}); } else
             * if(f.getFieldType().equals(Date.class)){ m.invoke(o, new
             * Object[]{ rs.getDate(f.getDataBaseName())}); } else
             * if(f.getFieldType().equals(Boolean.class)){ m.invoke(o, new
             * Object[]{ rs.getBoolean(f.getDataBaseName())}); } else
             * if(f.getFieldType().equals(Float.class)){ m.invoke(o, new
             * Object[]{ rs.getFloat(f.getDataBaseName())}); } else{ m.invoke(o,
             * new Object[]{ rs.getString(f.getDataBaseName())}); }
             */
        }

        return o;
    }

    public T load(ResultSet rs) throws Exception {
        return load(rs, true);
    }

    public List<T> loadList(ResultSet rs) throws Exception {
        List<T> lst = new ArrayList<T>();
        while (rs.next()) {
            lst.add(load(rs, false));
        }
        return lst;
    }

    private PreparedStatement getDeletePreparedStatement(T o) {
        if (listFields() == null) {
            //throw new Exception("Lista de campos não preenchidos");
            return null;
        }

        if (listFields().isEmpty()) {
            //throw new Exception("Lista de campos não preenchidos");
            return null;
        }

        String query = "DELETE FROM " + tableName() + " WHERE (1=1)";

        List<GenericDAOField> pkList = new ArrayList<GenericDAOField>();

        for (GenericDAOField f : listFields()) {
            if (f.isPrimaryKey()) {
                query += " AND " + f.getDatabaseFieldName() + " = ?";
                pkList.add(f);
            }
        }

        Class c = o.getClass();

        PreparedStatement ps = getPreparedStatement(query);
        int counter = 1;
        try {
            for (GenericDAOField pk : pkList) {
                Method m = c.getMethod(pk.getGetFunctionName(), new Class[]{});
                Object propertyValue = m.invoke(o, new Object[]{});

                ps.setObject(counter, propertyValue);

                /*
                 * if(f.getFieldType().equals(Integer.class)){
                 * ps.setInteger(counter, Integer.parseInt(propertyValue); }
                 * else if(f.getFieldType().equals(Date.class)){
                 * ps.setInteger(counter, Integer.parseInt(propertyValue); }
                 * else if(f.getFieldType().equals(Boolean.class)){
                 * ps.setInteger(counter, Integer.parseInt(propertyValue); }
                 * else if(f.getFieldType().equals(Float.class)){
                 * ps.setInteger(counter, Integer.parseInt(propertyValue); }
                 * else{ ps.setInteger(counter, Integer.parseInt(propertyValue);
                 * }
                 */

                counter++;
            }
        } catch (Exception ex) {
            return null;
        }


        return ps;
    }

    private PreparedStatement getInsertPreparedStatement(T o) {
        if (listFields() == null) {
            //throw new Exception("Lista de campos não preenchidos");
            return null;
        }

        if (listFields().isEmpty()) {
            //throw new Exception("Lista de campos não preenchidos");
            return null;
        }

        String query = "INSERT INTO " + tableName() + "(";
        String queryValues = "VALUES (";

        for (GenericDAOField f : listFields()) {
            if (f.isPrimaryKey() && f.getPrimaryKeyMode() == GenericDAOFieldPrimaryKey.IDENTITY) {
                continue;
            }
            query += f.getDatabaseFieldName() + ", ";
            queryValues += "?, ";
        }

        query = query.substring(0, query.length() - 2) + ")";
        queryValues = queryValues.substring(0, queryValues.length() - 2) + ")";

        query += " " + queryValues;

        Class c = o.getClass();

        PreparedStatement ps = getPreparedStatement(query);
        int counter = 1;

        try {
            for (GenericDAOField f : listFields()) {
                if (f.isPrimaryKey() && f.getPrimaryKeyMode() == GenericDAOFieldPrimaryKey.IDENTITY) {
                    continue;
                }

                Method m = c.getMethod(f.getGetFunctionName(), new Class[]{});
                Object propertyValue = m.invoke(o, new Object[]{});

                ps.setObject(counter, propertyValue);

                /*
                 * if(f.getFieldType().equals(Integer.class)){
                 * ps.setInteger(counter, Integer.parseInt(propertyValue); }
                 * else if(f.getFieldType().equals(Date.class)){
                 * ps.setInteger(counter, Integer.parseInt(propertyValue); }
                 * else if(f.getFieldType().equals(Boolean.class)){
                 * ps.setInteger(counter, Integer.parseInt(propertyValue); }
                 * else if(f.getFieldType().equals(Float.class)){
                 * ps.setInteger(counter, Integer.parseInt(propertyValue); }
                 * else{ ps.setInteger(counter, Integer.parseInt(propertyValue);
                 * }
                 */

                counter++;
            }
        } catch (Exception ex) {
            return null;
        }

        return ps;
    }

    private PreparedStatement getUpdatePreparedStatement(T o) {
        if (listFields() == null) {
            //throw new Exception("Lista de campos não preenchidos");
            return null;
        }

        if (listFields().isEmpty()) {
            //throw new Exception("Lista de campos não preenchidos");
            return null;
        }

        String query = "UPDATE " + tableName() + " SET ";
        List<GenericDAOField> pkList = new ArrayList<GenericDAOField>();

        for (GenericDAOField f : listFields()) {
            if (f.isPrimaryKey()) {
                pkList.add(f);
                if (f.getPrimaryKeyMode() != GenericDAOFieldPrimaryKey.NOTHING) {
                    continue;
                }
            }
            query += f.getDatabaseFieldName() + " = ?, ";
        }

        query = query.substring(0, query.length() - 2) + " WHERE (1=1) ";

        for (GenericDAOField f : pkList) {
            query += "AND " + f.getDatabaseFieldName() + " = ? ";
        }

        Class c = o.getClass();

        PreparedStatement ps = getPreparedStatement(query);
        int counter = 1;

        try {
            for (GenericDAOField f : listFields()) {
                if (f.isPrimaryKey() && f.getPrimaryKeyMode() != GenericDAOFieldPrimaryKey.NOTHING) {
                    continue;
                }

                Method m = c.getMethod(f.getGetFunctionName(), new Class[]{});
                Object propertyValue = m.invoke(o, new Object[]{});

                ps.setObject(counter, propertyValue);

                /*
                 * if(f.getFieldType().equals(Integer.class)){
                 * ps.setInteger(counter, Integer.parseInt(propertyValue); }
                 * else if(f.getFieldType().equals(Date.class)){
                 * ps.setInteger(counter, Integer.parseInt(propertyValue); }
                 * else if(f.getFieldType().equals(Boolean.class)){
                 * ps.setInteger(counter, Integer.parseInt(propertyValue); }
                 * else if(f.getFieldType().equals(Float.class)){
                 * ps.setInteger(counter, Integer.parseInt(propertyValue); }
                 * else{ ps.setInteger(counter, Integer.parseInt(propertyValue);
                 * }
                 */

                counter++;
            }


            for (GenericDAOField f : pkList) {
                Method m = c.getMethod(f.getGetFunctionName(), new Class[]{});
                Object propertyValue = m.invoke(o, new Object[]{});

                ps.setObject(counter, propertyValue);

                /*
                 * if(f.getFieldType().equals(Integer.class)){
                 * ps.setInteger(counter, Integer.parseInt(propertyValue); }
                 * else if(f.getFieldType().equals(Date.class)){
                 * ps.setInteger(counter, Integer.parseInt(propertyValue); }
                 * else if(f.getFieldType().equals(Boolean.class)){
                 * ps.setInteger(counter, Integer.parseInt(propertyValue); }
                 * else if(f.getFieldType().equals(Float.class)){
                 * ps.setInteger(counter, Integer.parseInt(propertyValue); }
                 * else{ ps.setInteger(counter, Integer.parseInt(propertyValue);
                 * }
                 */

                counter++;
            }
        } catch (Exception ex) {
            return null;
        }

        return ps;
    }
}
